#include <locale.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>

#include <gpgme.h>

#include "kylin-verify.h"
#include "kylin-activation.h"

#define BUF_SIZE 4096

/*
 * public key content and information 
 */
static public_key_info_t public_keys[] = 
{
	{
		{
			0x98, 0x8d, 0x04, 0x57, 0x42, 0xb1, 0x78, 0x01, 0x04, 0x00,
			0xa8, 0xf4, 0xa4, 0x7d, 0xc1, 0xd9, 0xdc, 0xd7, 0x90, 0xb3,
			0xa3, 0xf9, 0x9b, 0x67, 0xab, 0x34, 0x4a, 0x05, 0xf8, 0x62,
			0x95, 0x0a, 0x67, 0x00, 0x41, 0x91, 0x8c, 0xda, 0x44, 0x25,
			0x4f, 0x0d, 0xcb, 0x7f, 0xf4, 0x5a, 0x3f, 0x05, 0xc8, 0xf8,
			0x24, 0xf6, 0x19, 0xe5, 0xd6, 0xec, 0x11, 0x3a, 0x9d, 0xc0,
			0xcf, 0xa5, 0xfa, 0x08, 0xd2, 0xc4, 0xb3, 0xdf, 0x09, 0x40,
			0x57, 0x66, 0x6c, 0x7c, 0xcc, 0x50, 0xeb, 0x65, 0xfd, 0xef,
			0x82, 0xdf, 0x69, 0x31, 0x54, 0xf4, 0x85, 0x36, 0x54, 0x2d,
			0xc6, 0x25, 0xf6, 0x5c, 0x62, 0x10, 0xf9, 0x3c, 0xde, 0xb2,
			0x61, 0xce, 0x74, 0xd0, 0x29, 0x5c, 0x2d, 0xc9, 0x6e, 0x6b,
			0x1a, 0xd7, 0x36, 0x71, 0x50, 0x84, 0x3f, 0x1b, 0x49, 0xef,
			0xb0, 0x5e, 0xa1, 0x3a, 0xfd, 0x6c, 0x7a, 0x3b, 0x5f, 0xcd,
			0x1e, 0x62, 0x4e, 0x7e, 0x8d, 0x5f, 0x16, 0x3f, 0x00, 0x11,
			0x01, 0x00, 0x01, 0xb4, 0x36, 0x4b, 0x79, 0x6c, 0x69, 0x6e,
			0x20, 0x4f, 0x53, 0x20, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73,
			0x65, 0x20, 0x28, 0x6b, 0x79, 0x6c, 0x69, 0x6e, 0x20, 0x34,
			0x2e, 0x30, 0x2d, 0x32, 0x29, 0x20, 0x3c, 0x6f, 0x73, 0x5f,
			0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x40, 0x6b, 0x79,
			0x6c, 0x69, 0x6e, 0x6f, 0x73, 0x2e, 0x63, 0x6e, 0x3e, 0x88,
			0xb8, 0x04, 0x13, 0x01, 0x02, 0x00, 0x22, 0x05, 0x02, 0x57,
			0x42, 0xb1, 0x78, 0x02, 0x1b, 0x2f, 0x06, 0x0b, 0x09, 0x08,
			0x07, 0x03, 0x02, 0x06, 0x15, 0x08, 0x02, 0x09, 0x0a, 0x0b,
			0x04, 0x16, 0x02, 0x03, 0x01, 0x02, 0x1e, 0x01, 0x02, 0x17,
			0x80, 0x00, 0x0a, 0x09, 0x10, 0x2d, 0x1a, 0xfe, 0xb8, 0xbe,
			0x9c, 0xa8, 0xfb, 0x76, 0xc2, 0x03, 0xfd, 0x14, 0x81, 0x04,
			0xe3, 0x75, 0xbf, 0xb1, 0x14, 0x5e, 0x6e, 0x85, 0xf1, 0x89,
			0x30, 0x39, 0xe6, 0xdf, 0x7a, 0x30, 0xf5, 0xbc, 0x01, 0x01,
			0x1d, 0x9e, 0x61, 0x48, 0x63, 0x06, 0xbd, 0x22, 0x5c, 0x15,
			0x4f, 0x31, 0xb4, 0x69, 0x0c, 0x09, 0xd3, 0xa6, 0x8e, 0x06,
			0x91, 0x91, 0xee, 0x16, 0x69, 0x19, 0x99, 0x5f, 0x26, 0x3d,
			0xf6, 0x35, 0x3f, 0xc3, 0xfc, 0x2e, 0x8f, 0x72, 0xe8, 0x90,
			0xfa, 0x5b, 0x3e, 0x02, 0x2b, 0x50, 0x93, 0xf7, 0x5b, 0xbe,
			0x3b, 0xa5, 0x6d, 0x66, 0x9d, 0x94, 0xda, 0x8e, 0xce, 0x2b,
			0xae, 0x37, 0x43, 0xa0, 0xf6, 0xe6, 0x94, 0x1c, 0x2a, 0x53,
			0xfa, 0x6c, 0xcb, 0x93, 0xd8, 0x10, 0x61, 0x44, 0xb3, 0xb4,
			0xc4, 0xa0, 0x58, 0x44, 0xb9, 0x0a, 0x8f, 0x48, 0x20, 0xe6,
			0x8d, 0xed, 0x60, 0x66, 0x86, 0x56, 0x1d, 0x43, 0xe5, 0x85,
			0x2e, 0xa6, 0x06, 0x3c, 0x72,
		},
		385,
		"68E00802F372764566CE7D492D1AFEB8BE9CA8FB",
		"2D1AFEB8BE9CA8FB",
		1463988600,
	},
};

#define FINGER_PRINT (public_keys[0].fpr)
#define TIMESTAMP    (public_keys[0].timestamp)

/*
 * is_fingerprint_valid, check if the fingerprint string is valid for public keys
 * return 1 if valid, or 0 if not
 */
static int is_fingerprint_valid(const char* fpr, public_key_info_t* public_keys, int pubkey_number)
{
	int i;

	for(i = 0; i < pubkey_number; i++)
	{
		if(0 == strcmp(fpr, public_keys[i].fpr))
			return 1;
	}

	return 0;
}

/*
 * is_keyid_valid, check if the keyid string is valid for public keys
 * return 1 if valid, or 0 if not
 */
static int is_keyid_valid(const char* keyid, public_key_info_t* public_keys, int pubkey_number)
{
	int i;

	for(i = 0; i < pubkey_number; i++)
	{
		if(0 == strcmp(keyid, public_keys[i].keyid))
			return 1;
	}

	return 0;
}

/*
 * is_signature_with_valid_fingerprint, check if the signature is with valid fingerprint
 * return 1 if valid, or 0 if not
 */
static int is_signature_with_valid_fingerprint(gpgme_signature_t signature, public_key_info_t* public_keys, int pubkey_number)
{
	gpgme_signature_t sig = signature;

	if(!sig)
		return 0;

	while(sig)
	{
		if(!is_fingerprint_valid(sig->fpr, public_keys, pubkey_number) &&
		   !is_keyid_valid(sig->fpr, public_keys, pubkey_number))
			return 0;
		sig = sig->next;
	}

	return 1;
}

/*
 * is_signature_valid, check if the signature is valid
 * return 1 if valid, or 0 if not
 */
static int is_signature_valid(gpgme_ctx_t ctx)
{
	gpgme_verify_result_t status;

	// get the result of verification
	status = gpgme_op_verify_result (ctx);

	// check if there is a signature
	if (status->signatures)
	{
		/* When the signature is valid, the signatures->summary shows GPGME_SIGSUM_GREEN and GPGME_SIGSUM_VALID
                 * and signatures->status is GPG_ERR_NO_ERROR if the public key is fully trusted, 
                 * or only signatures->status is GPG_ERR_NO_ERROR.
                 */
		if (status->signatures->summary & GPGME_SIGSUM_GREEN ||
			status->signatures->summary & GPGME_SIGSUM_VALID ||
			gpg_err_code(status->signatures->status) == GPG_ERR_NO_ERROR)	
		{
			if(!is_signature_with_valid_fingerprint(status->signatures, public_keys,
			                  sizeof(public_keys) / sizeof(struct public_key_info)))
			{
				return INVALID_SIGNATURE;
			}

			return NO_ERROR;
		}

		/* Check if the public key is missing */
		if (status->signatures->summary & GPGME_SIGSUM_KEY_MISSING)
		{
			//fprintf(stderr, "没有找到有效公钥。\n");
			//fprintf(stderr, "无法验证LICENSE文件。\n");
			return NO_VALID_PUBLIC_KEY;
		}
		/* If the signature is failed to be verified, signatures->summary is GPGME_SIGSUM_RED,
                 * no matter the public key is fully trusted or not.
                 * */
		if(status->signatures->summary & GPGME_SIGSUM_RED ||
			gpg_err_code(status->signatures->status) == GPG_ERR_BAD_SIGNATURE ||
			gpg_err_code(status->signatures->status) != GPG_ERR_NO_ERROR)
		{
			//fprintf(stderr, "无效LICENSE文件\n");
			if(status->signatures->summary & GPGME_SIGSUM_RED)
				return SIGSUM_RED_ERROR;
			if(gpg_err_code(status->signatures->status) == GPG_ERR_BAD_SIGNATURE)
				return BAD_SIGNATURE_ERROR;
			if(gpg_err_code(status->signatures->status) != GPG_ERR_NO_ERROR)
			return SIGNATURE_STATUS_ERROR;
		}


	}

	return LICENSE_NO_SIGNATURE;
}

/* static void write_plain_text_to_file(gpgme_data_t plain, const char* output_file_name)
{
	FILE* output_file = NULL;
	char buf[BUF_SIZE];

	gpgme_error_t err;

	int ret;

	output_file = fopen (output_file_name, "w");

	ret = gpgme_data_seek (plain, 0, SEEK_SET);

	if (ret < 0)
		fail_if_err (gpgme_err_code_from_errno (err));


	while ((ret = gpgme_data_read (plain, buf, BUF_SIZE)) > 0)
	{
		fwrite (buf, ret, 1, output_file);
	}

	if(ret < 0)
		fail_if_err (gpgme_err_code_from_errno (errno));

	fclose (output_file);
}
*/

/*
 * custom_information, get plain text of custom information
 */
static char* custom_information(gpgme_data_t plain, unsigned long* custom_info_size)
{
	char* custom_info = NULL;
	int ret;
	unsigned long sum = 0;
	unsigned long length;

	
	// Get plain text length
	length = gpgme_data_seek (plain, 0, SEEK_END);

	// In case this plain text is too long,
	// text longer than BUF_SIZE is not allocated.
	if(length <= BUF_SIZE)
	{
		// Allocate memory
		custom_info = (char*)malloc(length);
		if(custom_info_size)
			*custom_info_size = length;

		if(custom_info)
		{
			ret = gpgme_data_seek (plain, 0, SEEK_SET);
			if (ret < 0)
			{
				//fprintf(stderr, "无法获取麒麟信息文件大小\n");
				free(custom_info);
				return NULL;
			}

			memset(custom_info, 0, length);

			// Place the content of plain into custom_info
			while((ret = gpgme_data_read(plain, &custom_info[sum], length  - sum)) > 0)
				sum += ret;

			if(ret < 0)
			{
				//fprintf(stderr, "无法获得麒麟信息文件内容\n");
				free(custom_info);
				return NULL;
			}

			// In case some error
			if(sum != length)
			{
				free(custom_info);
				custom_info = NULL;
				if(custom_info_size)
					*custom_info_size = 0;
			}
		}

	}

	return custom_info;
}

/*
static void display_custom_information(gpgme_data_t plain)
{
	char buf[BUF_SIZE];

	memset(buf, 0, BUF_SIZE);


	int ret;

	// Rewind the "plain" data object
	ret = gpgme_data_seek (plain, 0, SEEK_SET);

	if (ret < 0)
		fail_if_err (gpgme_err_code_from_errno (errno));

	printf("\n");

	// Read the contents of "plain" and place it into buf
	while ((ret = gpgme_data_read (plain, buf, BUF_SIZE)) > 0)
	{
		// display the contents of "buf" in stdout
		fprintf(stdout, "%s", buf);
	}

	if(ret < 0)
		fail_if_err (gpgme_err_code_from_errno (errno));
	

}
*/


/*
 * kylin_import_pubkey, import public key
 */
static int kylin_import_pubkey(gpgme_ctx_t ctx, public_key_info_t *public_keys, int pubkey_number)
{
	int i;
	gpgme_error_t err;
	gpgme_data_t public_key = NULL;

	
	for(i = 0; i < pubkey_number; ++i)
	{
		public_key = NULL;
		err = gpgme_data_new_from_mem(&public_key, public_keys[i].content, public_keys[i].content_size, 1);
		if(err)
			continue;

		gpgme_op_import(ctx, public_key);

		if(public_key)
		{
			gpgme_data_release(public_key);
			public_key = NULL;
		}
	}

	return NO_ERROR;
}

/*
 * gpg_verify, verify the signature of a file
 * return NO_ERROR if ok, or error number if not
 */
int gpg_verify(const char * file_to_verify, char** custom_info, unsigned long* custom_info_size)
{
	gpgme_ctx_t ctx = NULL;
	gpgme_error_t err;
	gpgme_data_t sig, plain = NULL;

	int result = -1;
	int no_license_file = 0;
	int plain_wrong = 0;
	/* Begin setup of GPGME*/
	gpgme_check_version (NULL);
	setlocale (LC_ALL, "");
	gpgme_set_locale (NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL));
	/* End setup of GPGME */

	// Create the GPGME Context
	err = gpgme_new (&ctx);
	// Error handling
	if(err)
	{
		result = GPGME_INITIALIZATION_ERROR;
		goto verify_out;
	}
	// Set the context to textmode
//	gpgme_set_textmode (ctx, 1);
	// Enable ASCII armor on the context
//	gpgme_set_armor (ctx, 1);

	// Create a data object pointing to the signature (cleared signed text).
	err = gpgme_data_new_from_file (&sig, file_to_verify, 1);
	if(err)
	{
		//printf("加载LINCENSE文件失败\n");
		result = LICENSE_LOAD_ERROR;
		no_license_file = 1;
		goto verify_out;
	}

	// Create a data object holding the text
	err = gpgme_data_new (&plain);
	if(err)
	{
		plain_wrong = 1;
		//printf("Failed to create plain text holder.\n");
		result = OTHER_ERROR;
		goto verify_out;
	}

	// import public key
	result = kylin_import_pubkey (ctx, public_keys, sizeof(public_keys) / sizeof(struct public_key_info));
	if(result != NO_ERROR)
		goto verify_out;

	// Verify the signature "sig",  achieve plain text and place it into "plain"
       	err = gpgme_op_verify (ctx, sig, NULL, plain);
	if(err != GPG_ERR_NO_ERROR)
	{
		//fprintf(stderr, "LICENSE文件验证失败\n");
		result = LICENSE_VERIFY_ERROR;
		goto verify_out;
	}

	// check if the signature is valid
	result = is_signature_valid (ctx);
	if((result == NO_ERROR) && custom_info)
		*custom_info = custom_information(plain, custom_info_size);


verify_out:
	// release data objects
	if(sig && !no_license_file)
		gpgme_data_release (sig);
	if(plain && !plain_wrong)
		gpgme_data_release (plain);

	// release context
	if(ctx)
		gpgme_release (ctx);

	return result;
}

/*
 * is_encrypt_valid, check if encryption with public key is valid
 */
static int is_encrypt_valid(gpgme_ctx_t ctx)
{
	gpgme_encrypt_result_t result = NULL;

	result = gpgme_op_encrypt_result(ctx);
	if(result->invalid_recipients)
	{
		if(0 == strcmp(FINGER_PRINT, result->invalid_recipients->fpr))
			return NO_VALID_PUBLIC_KEY;
	}

	return NO_ERROR;
}

/*
 * encrypted_information, get plain text of encrypted information
 */
static char* encrypted_information(gpgme_data_t encrypted, unsigned long* encrypted_message_size)
{
	char* encrypted_message = NULL;
	int ret;
	unsigned long sum = 0;
	unsigned long length = -1;

	length = gpgme_data_seek (encrypted, 0, SEEK_END);
	if(length <= 0)
		return NULL;

	if(encrypted_message_size)
		*encrypted_message_size = length;

	encrypted_message = (char*)malloc(1 + length * sizeof(char));
	if(encrypted_message)
	{
		ret = gpgme_data_seek(encrypted, 0, SEEK_SET);
		if(ret < 0)
		{
			free(encrypted_message);
			return NULL;
		}

		memset(encrypted_message, 0, length);
		encrypted_message[length] = '\0';

		while((ret = gpgme_data_read(encrypted, &encrypted_message[sum], length  - sum)) > 0)
			sum += ret;

		if(ret < 0)
		{
			free(encrypted_message);
			return NULL;
		}

		if(sum != length)
		{
			free(encrypted_message);
			encrypted_message = NULL;
			if(encrypted_message_size)
				*encrypted_message_size = 0;
		}
	}

	return encrypted_message;
}


/*
 * gpg_encrypt, encrypt with gpg
 * return NO_ERROR if ok, or error number if not
 */
int gpg_encrypt(const char* message_to_encrypt, char** encrypted_message, unsigned long* encrypted_message_size)
{
	gpgme_ctx_t ctx = NULL;
	gpgme_error_t err;
	gpgme_data_t plain, encrypted = NULL;
	gpgme_key_t key = NULL;
	gpgme_key_t recipients[2] = {NULL, NULL};
	unsigned long int cur_time;

	int result = -1;
	int plain_wrong = 0;
	int encrypted_wrong = 0;

	cur_time = time((time_t*) NULL);

	gpgme_check_version(NULL);
	setlocale(LC_ALL, "");
	gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));

	err = gpgme_new (&ctx);

	if(err)
	{
		result = GPGME_INITIALIZATION_ERROR;
		goto encrypt_out;
	}

	gpgme_set_armor(ctx, 1);

	err = gpgme_data_new_from_mem(&plain, message_to_encrypt, strlen(message_to_encrypt), 1);
	if(err)
	{
		result = UKEY_CONTENT_ERROR;
		plain_wrong = 1;
		goto encrypt_out;
	}

	err = gpgme_data_new(&encrypted);
	if(err)
	{
		encrypted_wrong = 1;
		result = OTHER_ERROR;
		goto encrypt_out;
	}

	result = kylin_import_pubkey (ctx, public_keys, sizeof(public_keys) / sizeof(struct public_key_info));
	if(result != NO_ERROR)
		goto encrypt_out;

	err = gpgme_get_key(ctx, FINGER_PRINT, &key, 0);
	if(err || !key)
	{
		if(cur_time < (unsigned long int)TIMESTAMP)
		{
			result =  SYSTEM_TIME_ERROR;
			goto encrypt_out;
		}
		result = NO_VALID_PUBLIC_KEY;
		goto encrypt_out;
	}

	recipients[0] = key;

	err = gpgme_op_encrypt(ctx, recipients, GPGME_ENCRYPT_ALWAYS_TRUST, plain, encrypted);
	if(err)
	{
		result = GPGME_ENCRYPT_ERROR;
		goto encrypt_out;
	}

	result = is_encrypt_valid (ctx);
	if(NO_ERROR != result)
		goto encrypt_out;

	if(encrypted_message)
		*encrypted_message = encrypted_information(encrypted, encrypted_message_size);

encrypt_out:
	if(plain && !plain_wrong)
		gpgme_data_release(plain);
	if(encrypted && !encrypted_wrong)
		gpgme_data_release(encrypted);

	if(ctx)
		gpgme_release (ctx);

	return result;
}

/*
 * kyinfo_verify, check if kyinfo file can be loaded
 * return 1 if succeed, or 0 if not
 */
int kyinfo_verify(const char* info_file)
{
	GKeyFile* key_file_kyinfo;
	
	key_file_kyinfo = key_file_load_from_file (info_file);
	if(!key_file_kyinfo)
		return 0;

	g_key_file_free(key_file_kyinfo);
	return 1;
}

#if 0
int main(int argc, char** argv)
{
	int result = 0;
	unsigned char* custom_info = NULL;
	unsigned long custom_info_size = 0;

	if (argc != 2)
	{
		printf("This programm is used to verify clear signature only.\n");
		printf("Usage: kylin-verify <signature file>\n");
		exit (1);
	}


	result = gpg_verify(argv[1], &custom_info, &custom_info_size);
	if(result)
	{
		printf("Good signature.\n");
		if(custom_info)
			printf("\n%-*.*s", (int)custom_info_size, (int)custom_info_size, custom_info);

	}

	return 0;
}
#endif

